{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Аггрегация разметки датасета BPS"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Аггрегация строится по следующей системе:\n",
    "\n",
    "1. Сбор размеченных пулов с Толоки. Возможны варианты:\n",
    "    - только общий пул нужно аггрегировать, тогда забирается только он\n",
    "    - часть данных находится в контрольных заданиях и экзамене, тогда к основному пулу добавляются данные задания\n",
    "2. Фильтрация разметчиков:\n",
    "    - в общем пуле есть некоторое количество заранее размеченных заданий - контрольных\n",
    "    - хорошим считается разметчик, который показывает `accuracy >= 0.5` на данных заданиях\n",
    "    - формируется список \"плохих\" разметчиков\n",
    "3. Аггрегация ответов разметчиков по заданиям:\n",
    "    - форматирование в заданиях может отличаться от изначального из-за выгрузки с Толоки\n",
    "    - учитываются только ответы \"хороших\" разметчиков\n",
    "    - аггрегация по подготовленным пулам - создается массив карточек вида {key: value}, где key - кортеж из всех значимых элементов задания, value - список из кортежей вида (user_id, answer)\n",
    "4. Голосование большинством по каждому заданию:\n",
    "    - минимально необходимое большинство составляет 3 голоса, так как такое большинство валидно для перекрытия 5\n",
    "    - по результату формируется датафрейм с заданиями и ответами\n",
    "5. Подгрузка оригинальных данных с разметкой в виде таблицы с заданиями и ответами\n",
    "6. Соединение таблиц:\n",
    "    - очистка форматирования в таблице с ответами разметчиков и в таблице с правильными ответами\n",
    "    - создание единых столбцов с полным заданием\n",
    "    - соединение таблиц по данному столбцу\n",
    "    - валидация размеров\n",
    "7. Подсчет метрик."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from collections import Counter"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сбор данных разметки и фильтрация разметчиков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Датасет проходил разметку одним пулом из 100 сгенерированных специально объектов в формате оригинального датасета."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "assignments = pd.read_csv('assignments_from_pool_41554142__29-09-2023.tsv', sep='\\t')\n",
    "skills = pd.read_csv('workerSkills.csv', sep='|')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разметчикам предлагалось на основании имеющейся скобочной последовательности ответить на вопрос, сбалансирована ли она или нет. \n",
    "\n",
    "Вход: \n",
    "- INPUT:parentheses (пример: `[ { ( [ ] ) } ]`).\n",
    "\n",
    "Выход:\n",
    "- OUTPUT:correct (0 или 1)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>INPUT:parentheses</th>\n",
       "      <th>OUTPUT:correct</th>\n",
       "      <th>GOLDEN:correct</th>\n",
       "      <th>HINT:text</th>\n",
       "      <th>HINT:default_language</th>\n",
       "      <th>ASSIGNMENT:link</th>\n",
       "      <th>ASSIGNMENT:task_id</th>\n",
       "      <th>ASSIGNMENT:assignment_id</th>\n",
       "      <th>ASSIGNMENT:task_suite_id</th>\n",
       "      <th>ASSIGNMENT:worker_id</th>\n",
       "      <th>ASSIGNMENT:status</th>\n",
       "      <th>ASSIGNMENT:started</th>\n",
       "      <th>ASSIGNMENT:submitted</th>\n",
       "      <th>ASSIGNMENT:accepted</th>\n",
       "      <th>ASSIGNMENT:reward</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[ ( } ) ) ] { [ ] ( { { [ ( ] } } ) ) {</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>https://platform.toloka.ai/task/41554142/00027...</td>\n",
       "      <td>00027a10de--6515e7f4e6499645e520ca19</td>\n",
       "      <td>00027a10de--651672354dc98f156e8fc965</td>\n",
       "      <td>00027a10de--651672354dc98f156e8fc95b</td>\n",
       "      <td>ec3328bcaa866d99022b38cf8e522237</td>\n",
       "      <td>APPROVED</td>\n",
       "      <td>2023-09-29T06:44:05.190</td>\n",
       "      <td>2023-09-29T06:44:22.420</td>\n",
       "      <td>2023-09-29T06:44:22.420</td>\n",
       "      <td>0.022</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                         INPUT:parentheses  OUTPUT:correct  GOLDEN:correct  \\\n",
       "0  [ ( } ) ) ] { [ ] ( { { [ ( ] } } ) ) {               0             NaN   \n",
       "\n",
       "   HINT:text  HINT:default_language  \\\n",
       "0        NaN                    NaN   \n",
       "\n",
       "                                     ASSIGNMENT:link  \\\n",
       "0  https://platform.toloka.ai/task/41554142/00027...   \n",
       "\n",
       "                     ASSIGNMENT:task_id              ASSIGNMENT:assignment_id  \\\n",
       "0  00027a10de--6515e7f4e6499645e520ca19  00027a10de--651672354dc98f156e8fc965   \n",
       "\n",
       "               ASSIGNMENT:task_suite_id              ASSIGNMENT:worker_id  \\\n",
       "0  00027a10de--651672354dc98f156e8fc95b  ec3328bcaa866d99022b38cf8e522237   \n",
       "\n",
       "  ASSIGNMENT:status       ASSIGNMENT:started     ASSIGNMENT:submitted  \\\n",
       "0          APPROVED  2023-09-29T06:44:05.190  2023-09-29T06:44:22.420   \n",
       "\n",
       "       ASSIGNMENT:accepted  ASSIGNMENT:reward  \n",
       "0  2023-09-29T06:44:22.420              0.022  "
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "assignments.head(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Фильтруем толокеров, которые дали меньше половины корректных ответов на контрольных заданиях."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Users total:  24\n",
      "Bad users: 3\n"
     ]
    }
   ],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "users_dict = defaultdict(lambda: defaultdict(int))\n",
    "\n",
    "for idx, row in assignments.iterrows():\n",
    "    para = row[0]\n",
    "\n",
    "    out = row[1]\n",
    "    \n",
    "    gold = row[2]\n",
    "\n",
    "    user = row[9]\n",
    "\n",
    "    if str(user) != \"nan\" and str(gold) != \"nan\":\n",
    "        if out == int(gold):\n",
    "            users_dict[user][\"good\"] += 1\n",
    "        else:\n",
    "            users_dict[user][\"bad\"] += 1\n",
    "\n",
    "print(\"Users total: \", len(users_dict))\n",
    "bad_users = []\n",
    "for key, value in users_dict.items():\n",
    "    percentage_good = value[\"good\"]/(value[\"good\"] + value[\"bad\"])\n",
    "    if percentage_good < 0.5:\n",
    "        bad_users.append(key)\n",
    "\n",
    "print(\"Bad users:\", len(bad_users))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3 из 24 разметчиков на контрольных заданиях показали слишком плохое качество, чтобы учитывать их ответы для расчета метрики."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь нужно оставить только основной пул. Контролки генерировались отдельно от самой подвыборки, поэтому их учитывать нельзя. На контрольных заданиях есть `GOLDEN:correct`. Также отсеиваем возможные баги Толоки, когда в строке может не быть задания - `INPUT:parentheses` содержит NaN."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "assignments_no_control = assignments[assignments['GOLDEN:correct'].isnull()]\n",
    "assignments_no_control_no_null = assignments_no_control[assignments_no_control['INPUT:parentheses'].notnull()]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посчитаем, сколько было затрачено на получение разметки тестовых данных без учета контрольных заданий, так как они могли проходиться неограниченное количество раз одним и тем же разметчиком."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "взвешенная цена айтема в тесте: 0.02\n",
      "потрачено на разметку теста: 10.172\n",
      "10.172 / 0.02\n"
     ]
    }
   ],
   "source": [
    "def w_sum(df):\n",
    "    idx = df.index.values\n",
    "    vals = df.values\n",
    "    summ = idx * vals\n",
    "    return summ.sum()\n",
    "\n",
    "d1 = assignments_no_control_no_null['ASSIGNMENT:reward'].value_counts(normalize=True)\n",
    "d2 = assignments_no_control_no_null['ASSIGNMENT:reward'].value_counts()\n",
    "print(f'взвешенная цена айтема в тесте: {round(w_sum(d1), 3)}')\n",
    "print(f'потрачено на разметку теста: {round(w_sum(d2), 3)}')\n",
    "print(f'{round(w_sum(d2), 3)} / {round(w_sum(d1), 3)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выделим, сколько составила средняя часовая ставка для разметки тестовой части датасета. Это будет простое среднее из следующих величин: количество заданий, которое разметчк может сделать за час на основе данного задания, помноженное на цену задания."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3.1774317196437925"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def get_hour_pay(df):\n",
    "    try:\n",
    "        times = pd.to_datetime(df['ASSIGNMENT:submitted']) - pd.to_datetime(df['ASSIGNMENT:started'])\n",
    "    except Exception as e:\n",
    "        times = []\n",
    "        for i in range(len(assignments_no_control_no_null)):\n",
    "            try:\n",
    "                start = pd.to_datetime(assignments_no_control_no_null['ASSIGNMENT:started'].iloc[i])\n",
    "            except Exception as e:\n",
    "                start = pd.to_datetime(assignments_no_control_no_null['ASSIGNMENT:started'].apply(lambda x: x.split('T')[1]).iloc[i])\n",
    "            try:\n",
    "                end = pd.to_datetime(assignments_no_control_no_null['ASSIGNMENT:submitted'].iloc[i])\n",
    "            except Exception as e:\n",
    "                start = pd.to_datetime(assignments_no_control_no_null['ASSIGNMENT:submitted'].apply(lambda x: x.split('T')[1]).iloc[i])\n",
    "            delta = end - start\n",
    "            times.extend([delta])\n",
    "        times = pd.Series(times)\n",
    "    sums = 3600 / times.apply(lambda x: x.seconds) * df['ASSIGNMENT:reward']\n",
    "    return sums.mean()\n",
    "\n",
    "get_hour_pay(assignments_no_control_no_null)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сбор ответов разметчиков и голосование"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Собираем ответы для каждого задания."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>INPUT:parentheses</th>\n",
       "      <th>OUTPUT:correct</th>\n",
       "      <th>GOLDEN:correct</th>\n",
       "      <th>HINT:text</th>\n",
       "      <th>HINT:default_language</th>\n",
       "      <th>ASSIGNMENT:link</th>\n",
       "      <th>ASSIGNMENT:task_id</th>\n",
       "      <th>ASSIGNMENT:assignment_id</th>\n",
       "      <th>ASSIGNMENT:task_suite_id</th>\n",
       "      <th>ASSIGNMENT:worker_id</th>\n",
       "      <th>ASSIGNMENT:status</th>\n",
       "      <th>ASSIGNMENT:started</th>\n",
       "      <th>ASSIGNMENT:submitted</th>\n",
       "      <th>ASSIGNMENT:accepted</th>\n",
       "      <th>ASSIGNMENT:reward</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[ ( } ) ) ] { [ ] ( { { [ ( ] } } ) ) {</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>https://platform.toloka.ai/task/41554142/00027...</td>\n",
       "      <td>00027a10de--6515e7f4e6499645e520ca19</td>\n",
       "      <td>00027a10de--651672354dc98f156e8fc965</td>\n",
       "      <td>00027a10de--651672354dc98f156e8fc95b</td>\n",
       "      <td>ec3328bcaa866d99022b38cf8e522237</td>\n",
       "      <td>APPROVED</td>\n",
       "      <td>2023-09-29T06:44:05.190</td>\n",
       "      <td>2023-09-29T06:44:22.420</td>\n",
       "      <td>2023-09-29T06:44:22.420</td>\n",
       "      <td>0.022</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>{ ] { ]</td>\n",
       "      <td>1</td>\n",
       "      <td>0.0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>https://platform.toloka.ai/task/41554142/00027...</td>\n",
       "      <td>00027a10de--6515e7e063c0d47637f1e106</td>\n",
       "      <td>00027a10de--651672354dc98f156e8fc965</td>\n",
       "      <td>00027a10de--651672354dc98f156e8fc95b</td>\n",
       "      <td>ec3328bcaa866d99022b38cf8e522237</td>\n",
       "      <td>APPROVED</td>\n",
       "      <td>2023-09-29T06:44:05.190</td>\n",
       "      <td>2023-09-29T06:44:22.420</td>\n",
       "      <td>2023-09-29T06:44:22.420</td>\n",
       "      <td>0.022</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                         INPUT:parentheses  OUTPUT:correct  GOLDEN:correct  \\\n",
       "0  [ ( } ) ) ] { [ ] ( { { [ ( ] } } ) ) {               0             NaN   \n",
       "1                                  { ] { ]               1             0.0   \n",
       "\n",
       "   HINT:text  HINT:default_language  \\\n",
       "0        NaN                    NaN   \n",
       "1        NaN                    NaN   \n",
       "\n",
       "                                     ASSIGNMENT:link  \\\n",
       "0  https://platform.toloka.ai/task/41554142/00027...   \n",
       "1  https://platform.toloka.ai/task/41554142/00027...   \n",
       "\n",
       "                     ASSIGNMENT:task_id              ASSIGNMENT:assignment_id  \\\n",
       "0  00027a10de--6515e7f4e6499645e520ca19  00027a10de--651672354dc98f156e8fc965   \n",
       "1  00027a10de--6515e7e063c0d47637f1e106  00027a10de--651672354dc98f156e8fc965   \n",
       "\n",
       "               ASSIGNMENT:task_suite_id              ASSIGNMENT:worker_id  \\\n",
       "0  00027a10de--651672354dc98f156e8fc95b  ec3328bcaa866d99022b38cf8e522237   \n",
       "1  00027a10de--651672354dc98f156e8fc95b  ec3328bcaa866d99022b38cf8e522237   \n",
       "\n",
       "  ASSIGNMENT:status       ASSIGNMENT:started     ASSIGNMENT:submitted  \\\n",
       "0          APPROVED  2023-09-29T06:44:05.190  2023-09-29T06:44:22.420   \n",
       "1          APPROVED  2023-09-29T06:44:05.190  2023-09-29T06:44:22.420   \n",
       "\n",
       "       ASSIGNMENT:accepted  ASSIGNMENT:reward  \n",
       "0  2023-09-29T06:44:22.420              0.022  \n",
       "1  2023-09-29T06:44:22.420              0.022  "
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "assignments.head(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100\n"
     ]
    }
   ],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "text_dict = defaultdict(list)\n",
    "\n",
    "for para, user, out in zip(\n",
    "    assignments_no_control_no_null[\"INPUT:parentheses\"], assignments_no_control_no_null[\"ASSIGNMENT:worker_id\"], \n",
    "    assignments_no_control_no_null[\"OUTPUT:correct\"]\n",
    "    ):\n",
    "    if user not in bad_users:\n",
    "        text_dict[(para)].append([\n",
    "                user,\n",
    "                {\"out\": out}\n",
    "        ])\n",
    "\n",
    "print(len(text_dict))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Counter({5: 91, 4: 8, 3: 1})"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keys = list(text_dict.keys())\n",
    "Counter([len(text_dict[keys[i]]) for i in range(len(keys))])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть 9 заданий с перекрытием меньше 5. Однако в них всех есть большинство, проголосовавшее за одинаковый ответ, потому не нужно оценивать скиллы разметчиков для вычисления итоговых меток."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "preds_full = {}\n",
    "for i in range(len(keys)):\n",
    "    ans = text_dict[keys[i]]\n",
    "    lst = [ans[j][1]['out'] for j in range(len(ans))]\n",
    "    cnt = Counter(lst)\n",
    "    if len(lst) == 5:\n",
    "        most = Counter([ans[j][1]['out'] for j in range(len(ans))]).most_common(1)[0][1]\n",
    "        if most >= 3:\n",
    "            res = Counter([ans[j][1]['out'] for j in range(len(ans))]).most_common(1)[0][0]\n",
    "            preds_full[keys[i]] = res\n",
    "    elif len(lst) <= 4:\n",
    "        most = Counter([ans[j][1]['out'] for j in range(len(ans))]).most_common(1)[0][1]\n",
    "        if most > 2:\n",
    "            res = Counter([ans[j][1]['out'] for j in range(len(ans))]).most_common(1)[0][0]\n",
    "            preds_full[keys[i]] = res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "100"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(preds_full)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "preds_full_df = pd.concat([pd.DataFrame(preds_full.keys(), columns=['parentheses']), pd.DataFrame(preds_full.values(), columns=['lb'])], axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Во всех 100 объектах согласованность достаточно высока."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>parentheses</th>\n",
       "      <th>lb</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[ { ( [ ] ) } ]</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>{ } [ ] [ ] ( )</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>[ ] [ ] [ ] [ ] [ ( ) { ( ) } ( ) ( ) ]</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>( ] } ]</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>[ { [ }</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                               parentheses  lb\n",
       "0                          [ { ( [ ] ) } ]   1\n",
       "1                          { } [ ] [ ] ( )   1\n",
       "2  [ ] [ ] [ ] [ ] [ ( ) { ( ) } ( ) ( ) ]   1\n",
       "3                                  ( ] } ]   0\n",
       "4                                  [ { [ }   0"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "preds_full_df.head(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данном датафрейме собраны все задания и результаты голосования по ним большинством."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сопоставление разметки и ground truth"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Забираем задания из датасета с правильными метками."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "res_df = pd.read_csv('full_with_ans.tsv', sep='\\t')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "res_df = res_df.rename({'INPUT:parentheses': 'parentheses', 'target': 'lb'}, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "После скачивания с Толоки в текстах рушится форматирование, потому нельзя просто сделать join двух табличек. Нужно убрать все \"лишнее\" форматирование сразу из двух табличек, чтобы остались только тексты, пунктуация и пробелы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def format_text(text):\n",
    "    text = (text.strip().replace('\\n', ' ').replace('\\t', ' ')\n",
    "            .replace('\\r', ' ').replace('  ', ' ').replace('  ', ' ')\n",
    "            .replace('  ', ' '))\n",
    "    return text\n",
    "\n",
    "res_df['parentheses'] = res_df['parentheses'].apply(format_text)\n",
    "preds_full_df['parentheses'] = preds_full_df['parentheses'].apply(format_text)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Делаем left join, чтобы соединить голосование и правильные метки для одних и тех же заданий."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "new = res_df.merge(preds_full_df, on='parentheses', how='left')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_valid = new[new['lb_y'].notna()].copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "100"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(new_valid)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>parentheses</th>\n",
       "      <th>lb_x</th>\n",
       "      <th>lb_y</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[ ) { { [ ( ) } ) { ] [ ) ] } { } ( ( ]</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                               parentheses  lb_x  lb_y\n",
       "0  [ ) { { [ ( ) } ) { ] [ ) ] } { } ( ( ]     0     0"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "new_valid.head(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Подсчет метрики"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если в правом столбце меток осталось 100 непустых строк, значит, форматирование было подчищено корректно, и ничего не потерялось."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_valid['lb_y'] = new_valid['lb_y'].astype(int)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.0"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(new_valid['lb_x'] == new_valid['lb_y']).mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`Accuracy = 1.0`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Анализ ошибок"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Начнем анализ с рассмотрения отдельных ответов разметчиков."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "new = assignments_no_control_no_null.iloc[:, [0, 1, 9]].rename({\n",
    "    'INPUT:parentheses': 'parentheses',\n",
    "    'OUTPUT:correct': 'label',\n",
    "    'ASSIGNMENT:worker_id': 'user'\n",
    "}, axis=1).copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "new = new.merge(res_df, on='parentheses', how='left')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "25"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(new['label'] != new['lb']).sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть всего 25 случаев, когда разметчик ответил неправильно на задание. Ни в одном задании неправильных ответов не было больше 2, потому голосованием большинством ошибки затерлись."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from scipy import stats"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на ошибки внимательнее."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "new['error'] = (new['label'] != new['lb']).astype(int)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Возможно, они связаны с длиной задания или количеством разных уникальных скобок (от 1 до 3)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "new['lens'] = new.parentheses.apply(lambda x: len(x.replace(' ', '')))\n",
    "new['types'] = new.parentheses.apply(lambda x: int(np.ceil(len(set(x.replace(' ', ''))) / 2)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Корреляция Пирсона</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Корреляция ошибок и длин последовательностей</th>\n",
       "      <td>0.092</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Корреляция и количества разных типов скобок</th>\n",
       "      <td>0.045</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                              Корреляция Пирсона\n",
       "Корреляция ошибок и длин последовательностей               0.092\n",
       "Корреляция и количества разных типов скобок                0.045"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pd.DataFrame({\n",
    "    \"Корреляция ошибок и длин последовательностей\": stats.pearsonr(new['lens'], new['error'])[0],\n",
    "    \"Корреляция и количества разных типов скобок\": stats.pearsonr(new['types'], new['error'])[0]\n",
    "    }, index=['Корреляция Пирсона']).round(3).T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ошибки носят случайный характер и не взаимосвязаны напрямую с длиной входной последовательности, а также с количеством разных типов скобок."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вероятно, можно оценить сложность каждого примера через то, сколько нужно сделать шагов простейшим алгоритмом решения задачи, а также длину нерешаемого остатка для некорректных скобочных последовательностей."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "def check(obj):\n",
    "    text = obj.replace(' ', '')\n",
    "    cnt = 0\n",
    "    while '()' in text or '[]' in text or '{}' in text:\n",
    "        if '()' in text:\n",
    "            text = text.replace('()', '')\n",
    "            cnt += 1\n",
    "        if '[]' in text:\n",
    "            text = text.replace('[]', '')\n",
    "            cnt += 1\n",
    "        if '{}' in text:\n",
    "            text = text.replace('{}', '')\n",
    "            cnt += 1\n",
    "    return (int(not text), cnt, text)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "new['path'] = new['parentheses'].apply(lambda x: check(x)[1])\n",
    "new['rest'] = new['parentheses'].apply(lambda x: len(check(x)[2]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Корреляция Пирсона</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Корреляция ошибок и количества простейших скобок</th>\n",
       "      <td>0.092</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Корреляция и количества оставшихся нерешаемых скобок</th>\n",
       "      <td>0.045</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                    Корреляция Пирсона\n",
       "Корреляция ошибок и количества простейших скобок                 0.092\n",
       "Корреляция и количества оставшихся нерешаемых с...               0.045"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pd.DataFrame({\n",
    "    \"Корреляция ошибок и количества простейших скобок\": stats.pearsonr(new['lens'], new['error'])[0],\n",
    "    \"Корреляция и количества оставшихся нерешаемых скобок\": stats.pearsonr(new['types'], new['error'])[0]\n",
    "    }, index=['Корреляция Пирсона']).round(3).T"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получается, действительно, есть слабая взаимосвязь между количеством шагов для решения задачи и вероятностью ошибки на таком примере. Небольшая отрицательная корреляция для длин остатков может означать, что в таких остатках есть однозначные признаки нерешаемости, например, только одна открывающая скобка определенного вида (без закрывающей)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
